package pp.infrastructure.redis;

import jakarta.annotation.PostConstruct;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import pp.infrastructure.exception.PPException;
import pp.infrastructure.exception.PPExceptionInfo;

import java.util.concurrent.TimeUnit;

@Component
public class RedisHelper {

    private static final Logger log = LoggerFactory.getLogger(RedisHelper.class);

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @PostConstruct
    private void init() {
        log.info("========== Redis Helper 组件注册成功 ==========");
    }

    //缓存读写：
    /**
     * 写入缓存方法
     * @param key       缓存的键    长度5~512
     * @param value     缓存的值    长度5~512
     * @param timeout   过期时间(毫秒) 最大允许保存12小时
     */
    public boolean setCache(String key, String value, long timeout) {
        try {
            if(key.length() < RedisConstant.MIN_KEY_LENGTH || key.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "插入缓存，输入 key 无效，长度需在5~512，key = " + key);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            if(value.length() < RedisConstant.MIN_KEY_LENGTH || value.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "插入缓存，输入 value 无效，长度需在5~512，value = " + value);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            if(timeout < 0 || timeout > 43200000) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "插入缓存，输入 timeout 无效，timeout = " + timeout);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MILLISECONDS);
            return true;
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    /**
     * 读取缓存数据
     * @param key 缓存的键 长度5~512
     * @return    缓存的值
     */
    public String getCache(String key) {
        try {
            if(key.length() < RedisConstant.MIN_KEY_LENGTH || key.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "读取缓存，输入 key 无效，长度需在5~512，key = " + key);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            return redisTemplate.opsForValue().get(key);
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    /**
     * 删除缓存数据
     * @param key 缓存的键 长度5~512
     */
    public boolean deleteCache(String key) {
        try {
            if(key.length() < RedisConstant.MIN_KEY_LENGTH || key.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "删除缓存，输入 key 无效，长度需在5~512，key = " + key);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            return Boolean.TRUE.equals(redisTemplate.delete(key));
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    /**
     * 数据是否存在
     * @param key 缓存的键 长度5~512
     */
    public boolean exists(String key) {
        try {
            if(key.length() < RedisConstant.MIN_KEY_LENGTH || key.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "判断缓存是否存在，输入 key 无效，长度需在5~512，key = " + key);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }

    /**
     * 自动续约加锁方法,调用此方法记得必须解锁,也就是调用unLock
     * @param prefix        锁键前缀，长度需在5~512
     * @param lockKey       锁的键，长度需在5~512
     * @param waitTime      等待获取锁的最大时间（毫秒） 必须在0~100ms之间
     * @param retryAttempts 重连最大次数 必须在0~10之间
     * @return              是否成功获取锁
     */
    public boolean tryLock(String prefix, String lockKey, long waitTime,long retryAttempts) {
        return tryLock(prefix, lockKey, waitTime, RedisConstant.WATCH_DOG_LEASETIME, retryAttempts);
    }

    //分布式锁：
    /**
     * 手动设置过期时间且不启动自动续约加锁方法
     * @param prefix        锁键前缀，长度需在5~512
     * @param lockKey       锁的键，长度需在5~512
     * @param waitTime      等待获取锁的最大时间（毫秒） 必须在0~100ms之间
     * @param leaseTime     锁过期时间（毫秒） 必须在0~30000ms之间
     * @param retryAttempts 重连最大次数 必须在0~10之间
     * @return              是否成功获取锁
     */
    public boolean tryLock(String prefix, String lockKey, long waitTime, long leaseTime,long retryAttempts) {
        try {
            if(prefix.length() + lockKey.length() < RedisConstant.MIN_KEY_LENGTH || prefix.length() + lockKey.length() > RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "插入缓存，输入 prefix 或 lockKey 无效，长度需在5~512，prefix = " + prefix + "  lockKey = " + lockKey);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            if ((leaseTime < RedisConstant.MIN_LEASETIME || leaseTime > RedisConstant.MAX_LEASETIME)&&(leaseTime != RedisConstant.WATCH_DOG_LEASETIME)){ /* 参数检查 */
                log.error(String.valueOf(PPExceptionInfo.LOCK_LEASETIME_IS_NOT_ALLOWED));
                throw new PPException(PPExceptionInfo.LOCK_LEASETIME_IS_NOT_ALLOWED);
            }
            if (waitTime < RedisConstant.MIN_WAITTIME || waitTime > RedisConstant.MAX_WAITTIME){ /* 参数检查 */
                log.error(String.valueOf(PPExceptionInfo.LOCK_WAITTIME_IS_NOT_ALLOWED));
                throw new PPException(PPExceptionInfo.LOCK_WAITTIME_IS_NOT_ALLOWED);
            }
            if (retryAttempts < RedisConstant.MIN_RETRYATTEMPTS || retryAttempts > RedisConstant.MAX_RETRYATTEMPTS){ /* 参数检查 */
                log.error(String.valueOf(PPExceptionInfo.LOCK_RETRYATTEMPTS_IS_NOT_ALLOWED));
                throw new PPException(PPExceptionInfo.LOCK_RETRYATTEMPTS_IS_NOT_ALLOWED);
            }
            RLock lock = redissonClient.getLock(prefix + lockKey);
            if(leaseTime != RedisConstant.WATCH_DOG_LEASETIME){
                for (int i = 0; i < retryAttempts; i++){
                    if (lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS)) {
                        return true;
                    }
                }
            } else {
                for (int i = 0; i < retryAttempts; i++) {
                    if (lock.tryLock(waitTime, TimeUnit.MILLISECONDS)) {
                        return true;
                    }
                }
            }
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
        log.error(String.valueOf(PPExceptionInfo.LOCK_FAILED_TO_ACQUIRE_LOCK));
        throw new PPException(PPExceptionInfo.LOCK_FAILED_TO_ACQUIRE_LOCK);
    }

    /**
     * 解锁方法
     * @param prefix    锁键前缀, 长度需在5~512
     * @param lockKey   锁的键, 长度需在5~512
     * @return          解锁是否成功
     */
    public boolean unLock(String prefix, String lockKey) {
        try {
            if(prefix.length() + lockKey.length()< RedisConstant.MIN_KEY_LENGTH || prefix.length() + lockKey.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "插入缓存，输入 prefix 或 lockKey 无效，长度需在5~512，prefix = " + prefix + "  lockKey = " + lockKey);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            RLock lock = redissonClient.getLock(prefix + lockKey);
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                return true;
            }
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
        log.error(String.valueOf(PPExceptionInfo.LOCK_IS_NOT_EXIST));
        throw new PPException(PPExceptionInfo.LOCK_IS_NOT_EXIST);
    }

    /**
     * 解锁方法
     * @param prefix    锁键前缀, 长度需在5~512
     * @param lockKey   锁的键, 长度需在5~512
     * @return          返回锁还有多久过期（毫秒）
     */
    public long getLockTime(String prefix, String lockKey) {
        try {
            if(prefix.length() + lockKey.length()< RedisConstant.MIN_KEY_LENGTH || prefix.length() + lockKey.length()>RedisConstant.MAX_KEY_LENGTH) {
                log.error("{}.{} --> {};", this.getClass().getName(), new Object(){}.getClass().getEnclosingMethod().getName()
                        , "插入缓存，输入 prefix 或 lockKey 无效，长度需在5~512，prefix = " + prefix + "  lockKey = " + lockKey);
                throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_INNER_PARAM_INVALID);
            }
            RLock lock = redissonClient.getLock(prefix + lockKey);
            return lock.remainTimeToLive();
        } catch (RuntimeException e) {
            log.error(e.getMessage(), e);
            throw new PPException(PPExceptionInfo.GENERIC_SYSTEM_FAIL);
        }
    }
}
